---
title: Scripts and event handling
description: >-
  How to add client-side interactivity to Astro components using native browser
  JavaScript APIs.
i18nReady: true
---
import ReadMore from '~/components/ReadMore.astro'

You can send JavaScript to the browser and add functionality to your Astro components using `<script>` tags in the component template.

Scripts add interactivity to your site, such as handling events or updating content dynamically, without the need for a [UI framework](/en/guides/framework-components/) like React, Svelte, or Vue. This avoids the overhead of shipping framework JavaScript and doesn't require you to know any additional framework to create a full-featured website or application.

## Client-Side Scripts

Scripts can be used to add event listeners, send analytics data, play animations, and everything else JavaScript can do on the web.

Astro automatically enhances the HTML standard `<script>` tag with bundling, TypeScript, and more. See [how astro processes scripts](#script-processing) for more details.

```astro title="src/components/ConfettiButton.astro"
<button data-confetti-button>Celebrate!</button>

<script>
  // Import from npm package.
  import confetti from 'canvas-confetti';

  // Find our component DOM on the page.
  const buttons = document.querySelectorAll('[data-confetti-button]');

  // Add event listeners to fire confetti when a button is clicked.
  buttons.forEach((button) => {
    button.addEventListener('click', () => confetti());
  });
</script>
```

<ReadMore>See [when your scripts will not be processed](#unprocessed-scripts) to troubleshoot script behavior, or to learn how to opt-out of this processing intentionally.</ReadMore>


## Script processing

By default, Astro processes `<script>` tags that contain no attributes (other than `src`) in the following ways:

- **TypeScript support:** All scripts are TypeScript by default.
- **Import bundling:** Import local files or npm modules, which will be bundled together.
- **Type Module:** Processed scripts become [`type="module"`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) automatically.
- **Deduplication:** If a component that contains a `<script>` is used multiple times on a page, the script will only be included once.
- **Automatic inlining:** If the script is small enough, Astro will inline it directly into the HTML to reduce the number of requests.
```astro title="src/components/Example.astro"
<script>
  // Processed! Bundled! TypeScript!
  // Importing local scripts and from npm packages works.
</script>
```

### Unprocessed scripts

Astro will not process a `<script>` tag if it has any attribute other than `src`.

You can add the [`is:inline`](/en/reference/directives-reference/#isinline) directive to intentionally opt out of processing for a script.

```astro title="src/components/InlineScript.astro" "is:inline"
<script is:inline>
  // Will be rendered into the HTML exactly as written!
  // Not transformed: no TypeScript and no import resolution by Astro.
  // If used inside a component, this code is duplicated for each instance.
</script>
```

### Include JavaScript files on your page

You may want to write your scripts as separate `.js`/`.ts` files or need to reference an external script on another server. You can do this by referencing these in a `<script>` tag’s `src` attribute.

#### Import local scripts

**When to use this:** when your script lives inside of `src/`.

Astro will process these scripts according to the [script processing rules](#script-processing).

```astro title="src/components/LocalScripts.astro"
<!-- relative path to script at `src/scripts/local.js` -->
<script src="../scripts/local.js"></script>

<!-- also works for local TypeScript files -->
<script src="./script-with-types.ts"></script>
```

#### Load external scripts

**When to use this:** when your JavaScript file lives inside of `public/` or on a CDN.

To load scripts outside of your project's `src/` folder, include the `is:inline` directive. This approach skips the JavaScript processing, bundling, and optimizations that are provided by Astro when you import scripts as described above.

```astro title="src/components/ExternalScripts.astro" "is:inline"
<!-- absolute path to a script at `public/my-script.js` -->
<script is:inline src="/my-script.js"></script>

<!-- full URL to a script on a remote server -->
<script is:inline src="https://my-analytics.com/script.js"></script>
```

## Common script patterns

### Handle `onclick` and other events

Some UI frameworks use custom syntax for event handling like `onClick={...}` (React/Preact) or `@click="..."` (Vue). Astro follows standard HTML more closely and does not use custom syntax for events.

Instead, you can use [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) in a `<script>` tag to handle user interactions.

```astro title="src/components/AlertButton.astro"
<button class="alert">Click me!</button>

<script>
  // Find all buttons with the `alert` class on the page.
  const buttons = document.querySelectorAll('button.alert');

  // Handle clicks on each button.
  buttons.forEach((button) => {
    button.addEventListener('click', () => {
      alert('Button was clicked!');
    });
  });
</script>
```

If you have multiple `<AlertButton />` components on a page, Astro will not run the script multiple times. Scripts are bundled and only included once per page. Using `querySelectorAll` ensures that this script attaches the event listener to every button with the `alert` class found on the page.


### Web components with custom elements

You can create your own HTML elements with custom behavior using the Web Components standard. Defining a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) in a `.astro` component allows you to build interactive components without needing a UI framework library.

In this example, we define a new `<astro-heart>` HTML element that tracks how many times you click the heart button and updates the `<span>` with the latest count.

```astro title="src/components/AstroHeart.astro"
<!-- Wrap the component elements in our custom element “astro-heart”. -->
<astro-heart>
  <button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>

<script>
  // Define the behaviour for our new type of HTML element.
  class AstroHeart extends HTMLElement {
    connectedCallback() {
      let count = 0;

      const heartButton = this.querySelector('button');
      const countSpan = this.querySelector('span');

      // Each time the button is clicked, update the count.
			heartButton.addEventListener('click', () => {
        count++;
        countSpan.textContent = count.toString();
      });
		}
  }

  // Tell the browser to use our AstroHeart class for <astro-heart> elements.
  customElements.define('astro-heart', AstroHeart);
</script>
```

There are two advantages to using a custom element here:

1. Instead of searching the whole page using `document.querySelector()`, you can use `this.querySelector()`, which only searches within the current custom element instance. This makes it easier to work with only the children of one component instance at a time.

2. Although a `<script>` only runs once, the browser will run our custom element’s `connectedCallback()` method each time it finds `<astro-heart>` on the page. This means you can safely write code for one component at a time, even if you intend to use this component multiple times on a page.

<ReadMore>You can learn more about custom elements in [web.dev’s Reusable Web Components guide](https://web.dev/custom-elements-v1/) and [MDN’s introduction to custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements).</ReadMore>


### Pass frontmatter variables to scripts

In Astro components, the code in [the frontmatter](/en/basics/astro-components/#the-component-script) (between the `---` fences) runs on the server and is not available in the browser.

To pass server-side variables to client-side scripts, store them in [`data-*` attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) on HTML elements. Scripts can then access these values using the `dataset` property.

In this example component, a `message` prop is stored in a `data-message` attribute, so the custom element can read `this.dataset.message` and get the value of the prop in the browser.

```astro title="src/components/AstroGreet.astro" {2} /data-message={.+}/ "this.dataset.message"
---
const { message = 'Welcome, world!' } = Astro.props;
---

<!-- Store the message prop as a data attribute. -->
<astro-greet data-message={message}>
  <button>Say hi!</button>
</astro-greet>

<script>
  class AstroGreet extends HTMLElement {
    connectedCallback() {
      // Read the message from the data attribute.
      const message = this.dataset.message;
      const button = this.querySelector('button');
      button.addEventListener('click', () => {
        alert(message);
      });
		}
  }

  customElements.define('astro-greet', AstroGreet);
</script>
```

Now we can use our component multiple times and be greeted by a different message for each one.

```astro title="src/pages/example.astro"
---
import AstroGreet from '../components/AstroGreet.astro';
---

<!-- Use the default message: “Welcome, world!” -->
<AstroGreet />

<!-- Use custom messages passed as a props. -->
<AstroGreet message="Lovely day to build components!" />
<AstroGreet message="Glad you made it! 👋" />
```

:::tip[Did you know?]
This is actually what Astro does behind the scenes when you pass props to a component written using a UI framework like React! For components with a `client:*` directive, Astro creates an `<astro-island>` custom element with a `props` attribute that stores your server-side props in the HTML output.
:::

### Combining scripts and UI Frameworks

Elements rendered by a UI framework may not be available yet when a `<script>` tag executes. If your script also needs to handle [UI framework components](/en/guides/framework-components/), using a custom element is recommended.
